﻿/*************************************
 * Creator:SW-PeiYang
 * DateTime:2023/12/8 10:43:04
 * Description:<Description>
 * CopyRight:中广核研究院有限公司
 * ***********************************/

using Cpjit.SunnyPaine.Toolkit.Net.Sockets.Model;
using System;
using System.Collections.Generic;
using System.Net.Sockets;

namespace Cpjit.SunnyPaine.Toolkit.Net.Sockets.IDataHandlingApdapter.TCP
{
    /// <summary>
    /// 提供一个指定长度数据的数据处理适配器。
    /// <para>
    /// 由包头+数据组成。包头包含该数据帧的有效数据的长度。
    /// 作用原理为：数据帧总长度和TCP的数据Buffer大小相同，所以每次TCP的Buffer只能容下一条数据帧，以此保证每次只有一条数据，不会粘包拆包。
    /// </para>
    /// </summary>
    public class FixLengthDataHandlingAdapter : AbstractDataHandlingApdapter
    {
        #region 私有变量
        /// <summary>
        /// 包剩余长度。
        /// </summary>
        private int surPlusLength = 0;
        #endregion


        #region 构造方法
        /// <summary>
        /// 使用默认的参数创建<see cref="FixLengthDataHandlingAdapter"/>的实例。
        /// </summary>
        public FixLengthDataHandlingAdapter() : this(int.MaxValue)
        { }

        /// <summary>
        /// 使用指定的参数创建<see cref="FixLengthDataHandlingAdapter"/>的实例。
        /// </summary>
        /// <param name="capacity"></param>
        public FixLengthDataHandlingAdapter(int capacity)
        {
            this.Capacity = capacity;
            this.isRunning = true;
        }
        #endregion


        #region 公共方法，AbstractDataHandlingApdapter成员
        /// <summary>
        /// 消息接收前处理。
        /// </summary>
        /// <param name="session"></param>
        public override void PreviewReceived(SocketSession session)
        {
            if (isRunning == false)
            {
                return;
            }

            if (session.BufferSize > this.Capacity + 4)
            {
                throw new IndexOutOfRangeException("接收的消息长度大于固定容器的长度。");
            }
            if (this.UseCacheTime && DateTime.Now - this.lastCacheTime > this.cacheTimeout)
            {
                this.tempData?.Reset();
                this.tempData = null;
                this.surPlusLength = 0;
            }

            this.lastCacheTime = DateTime.Now;
            byte[] buffer = session.Buffer;
            int length = session.BufferSize;
            if (this.tempData == null)
            {
                this.SplitPackage(session, buffer, 0, length);
            }
            else
            {
                if (this.surPlusLength == length)
                {
                    this.tempData.WriteValue(buffer, 0, this.surPlusLength);
                    this.PreviewReceiveHandle(session, this.tempData);
                    this.tempData = null;
                    this.surPlusLength = 0;
                }
                else if (this.surPlusLength < length)
                {
                    this.tempData.WriteValue(buffer, 0, this.surPlusLength);
                    this.PreviewReceiveHandle(session, this.tempData);
                    this.tempData = null;
                    this.SplitPackage(session, buffer, this.surPlusLength, length);
                }
                else
                {
                    this.tempData.WriteValue(buffer, 0, length);
                    this.surPlusLength -= length;
                }
            }
        }

        /// <summary>
        /// 消息发送前处理。
        /// </summary>
        /// <param name="remote">远端Socket实例。</param>
        /// <param name="datas">要发送的数据。</param>
        /// <param name="isAsync">是否异步发送。true：异步，false：同步。</param>
        public override void PreviewSend(Socket remote, byte[] datas, bool isAsync)
        {
            byte[] packageDatas = this.PreviewSendHandle(datas);
            base.PreviewSend(remote, packageDatas, isAsync);
        }

        /// <summary>
        /// 消息发送前处理。
        /// </summary>
        /// <param name="remotes">远端Socket实例。</param>
        /// <param name="datas">要发送的数据。</param>
        /// <param name="isAsync">是否异步发送。true：异步，false：同步。</param>
        public override void PreviewSend(List<Socket> remotes, byte[] datas, bool isAsync)
        {
            byte[] packageDatas = this.PreviewSendHandle(datas);
            base.PreviewSend(remotes, packageDatas, isAsync);
        }
        #endregion


        #region 私有方法
        /// <summary>
        /// 拆包。
        /// </summary>
        /// <param name="session"></param>
        /// <param name="dataBuffer"></param>
        /// <param name="offset"></param>
        /// <param name="dataLength"></param>
        private void SplitPackage(SocketSession session, byte[] dataBuffer, int offset, int dataLength)
        {
            //理论不会出现分包、拆包情况，但为了应对非正常情况，需要做判断
            while (offset < dataLength)
            {
                if (dataLength - offset >= this.Capacity)
                {
                    ByteBlock block = new ByteBlock(this.Capacity);
                    block.WriteValue(dataBuffer, offset, this.Capacity);
                    this.PreviewReceiveHandle(session, block);
                    this.surPlusLength = 0;
                }
                else//分包情况
                {
                    this.tempData = new ByteBlock(this.Capacity);
                    this.surPlusLength = this.Capacity - (dataLength - offset);
                    this.tempData.WriteValue(dataBuffer, offset, dataLength - offset);
                }
                offset += this.Capacity;
            }
        }

        private void PreviewReceiveHandle(SocketSession session, ByteBlock block)
        {
            try
            {
                int dataLength = block.ReadInt();
                byte[] datas = block.ReadByte(dataLength);
                this.GoReceivedHandle(session, datas);
            }
            finally
            {
                block.Reset();
            }
        }

        private byte[] PreviewSendHandle(byte[] datas)
        {
            if (datas.Length > this.Capacity)
            {
                throw new IndexOutOfRangeException("发送的数据包长度大于固定容器的长度。");
            }
            int dataPackageLength = this.Capacity + 4;
            ByteBlock block = new ByteBlock(dataPackageLength);
            block.WriteValue(datas.Length);
            block.WriteValue(datas);
            for (int i = datas.Length + 4; i < dataPackageLength; i++)
            {
                byte v = 0;
                block.WriteValue(v);
            }
            return block.ToAarray();
        }
        #endregion
    }
}
